package homeTask3;

import java.io.Closeable;
import java.io.File;
import java.io.FileWriter;
import java.io.Flushable;
import java.io.IOException;
import java.io.PrintStream;
import java.util.HashMap;

public class Logger{
	private static Logger logger;
	private static File logFile;
	private static int countLogFiles = 1; //number of log files
	private StringBuffer sb;
	private long startTime;
	private String lineSeparator;
	private static boolean flushed;
	private int bufSize = 1000;
	private boolean newSessionOpened = false;
	
	private final int KB_SIZE = 1024; 	//size of 1 kB block
	private final int MAX_LOG_SIZE = 5; 	//max size of log file in kB
	
	private static LogLevel curLevel;
	
	public static enum LogLevel{
		DEBUG,
		INFO,
		WARN,
		ERROR,
		FATAL_ERROR;
		
		@Override 
		public String toString() {
		   String str = super.toString();
		   return "[" + str + "]";
		}		
		
		private static HashMap<LogLevel, Integer> levelPriority 
			= new HashMap<Logger.LogLevel, Integer>();
		
		/**
		 * @return true if current level of logging is higher in priority then level in args
		 * */
		public boolean equalsOrHigherThen(LogLevel lev){
			levelPriority.put(DEBUG, 1);
			levelPriority.put(INFO, 2);
			levelPriority.put(WARN, 3);
			levelPriority.put(ERROR, 4);
			levelPriority.put(FATAL_ERROR, 5);			
			
			return levelPriority.get(this) >= levelPriority.get(lev);
		}
	}
	
	public static LogLevel getLogLever(){
		return curLevel;
	}
	
	public static void setLevel(LogLevel level){
		curLevel = level;
	}
	
	private Logger(LogLevel level){
		curLevel = level;
		logFile = new File("logFile" + countLogFiles + ".log");
		sb = new StringBuffer(bufSize);
		startTime = System.currentTimeMillis();
		lineSeparator = System.getProperty("line.separator");
		flushed = false;
	}
	
	private void openNewSession(){
		newSessionOpened = true;
		for(int i = 0; i < 20; i++)
			sb.append("---");
		sb.append(lineSeparator + "New session: " + startTime + lineSeparator + lineSeparator);
		sb.append("Time Level\tMessage" + lineSeparator);
	}
	
	public static synchronized Logger getLogger(LogLevel level){
		if(logger == null){
			logger = new Logger(level);
		}
		return logger;
	}
	
	public synchronized void setBufferSize(int size){
		flush();
		bufSize = size;
		sb = new StringBuffer(size);
	}
	
	private synchronized void rotate(){
		String logName = logFile.getAbsolutePath();
		String cuttedName = logName.substring(0, logName.length() - 5);
		File oldLogFile = new File(cuttedName + countLogFiles + ".log");
		countLogFiles++;
		System.out.println(countLogFiles);
		logFile.renameTo(oldLogFile);
		logFile = new File(cuttedName + countLogFiles + ".log");
		System.out.println("new log: " + logFile.getName());
	}
	
	private void checkFileSize(){
		System.out.println(logFile.length());
		if(logFile.length() / KB_SIZE >= MAX_LOG_SIZE)
			rotate();
	}
	
	public synchronized void flush(){
		if(flushed)
			return;
		
//		sb.append("Current time of flushing: " + (System.currentTimeMillis() - startTime) + lineSeparator);
		FileWriter fw = null;
		try {
			fw = new FileWriter(logFile, true);
			fw.write(sb.toString());
			fw.flush();
		} catch (IOException e) {
			System.out.println("Exception occured while writing log file");
			e.printStackTrace();
		}finally{
			if(fw != null){
				try {
					fw.close();
				} catch (IOException e) {
					System.out.println("Exception while writing log file: exception occured while trying to close the file");
					e.printStackTrace();
				}
			}
		}
		flushed = true;
		sb.delete(0, sb.length());
		checkFileSize();
	}
	
	private synchronized void checkBufSize(boolean canFlush){
		if(canFlush && sb.length() > bufSize)
			flush();
	}
	
	private synchronized void addMessage(LogLevel level, String message, PrintStream out, boolean canFlush){
		addMessage(level, message, out, null, canFlush);
	}
	
	private synchronized void addMessage(LogLevel level, String message, PrintStream out, 
										Throwable ex, boolean canFlush){
		//check level of logging
		if(!level.equalsOrHigherThen(curLevel))
			return;
		
		//open new session if it's necessary
		if(newSessionOpened == false)
			openNewSession();
				
		StringBuffer curMessage = out == null ? sb : new StringBuffer();
		
		flushed = false;
		long time = System.currentTimeMillis() - startTime;
		
		if(ex != null){
			StringBuilder stackTrace = new StringBuilder();
			for(StackTraceElement el: ex.getStackTrace())	
				stackTrace.append("\t\t" + el + lineSeparator);
				curMessage.append(time + ":\t" + level + ": " + message + lineSeparator + "\tException: " + ex
					+ lineSeparator + "\tStack trace: " + lineSeparator + stackTrace + lineSeparator + lineSeparator);
		}
		else
			curMessage.append(time + ":\t" + level + ": " + message + lineSeparator + lineSeparator);
		
		if(out != null){
			out.append(curMessage.toString());
			sb.append(curMessage.toString());
		}
			
		checkBufSize(canFlush);
	}
	
	public void debug(String message, PrintStream out, boolean canFlush){
		addMessage(LogLevel.DEBUG, message, out, canFlush);
	}
	
	public void debug(String message, PrintStream out, Throwable ex, boolean canFlush){
		addMessage(LogLevel.DEBUG, message, out, ex, canFlush);
	}
	
	public void info(String message, PrintStream out, boolean canFlush){
		addMessage(LogLevel.INFO, message, out, canFlush);
	}
	
	public void info(String message, PrintStream out, Throwable ex, boolean canFlush){
		addMessage(LogLevel.INFO, message, out, ex, canFlush);
	}
	public void warn(String message, PrintStream out, boolean canFlush){
		addMessage(LogLevel.WARN, message, out, canFlush);
	}
	
	public void warn(String message, PrintStream out, Throwable ex, boolean canFlush){
		addMessage(LogLevel.WARN, message, out, ex, canFlush);
	}
	public void error(String message, PrintStream out, boolean canFlush){
		addMessage(LogLevel.ERROR, message, out, canFlush);
	}
	
	public void error(String message, PrintStream out, Throwable ex, boolean canFlush){
		addMessage(LogLevel.ERROR, message, out, ex, canFlush);
	}
	public void fatalError(String message, PrintStream out, boolean canFlush){
		addMessage(LogLevel.FATAL_ERROR, message, out, canFlush);
	}
	
	public void fatalError(String message, PrintStream out, Throwable ex, boolean canFlush){
		addMessage(LogLevel.FATAL_ERROR, message, out, ex, canFlush);
	}
	
	public void debug(String message, PrintStream out){
		addMessage(LogLevel.DEBUG, message, out, true);
	}
	
	public void debug(String message, PrintStream out, Throwable ex){
		addMessage(LogLevel.DEBUG, message, out, ex, true);
	}
	
	public void info(String message, PrintStream out){
		addMessage(LogLevel.INFO, message, out, true);
	}
	
	public void info(String message, PrintStream out, Throwable ex){
		addMessage(LogLevel.INFO, message, out, ex, true);
	}
	public void warn(String message, PrintStream out){
		addMessage(LogLevel.WARN, message, out, true);
	}
	
	public void warn(String message, PrintStream out, Throwable ex){
		addMessage(LogLevel.WARN, message, out, ex, true);
	}
	public void error(String message, PrintStream out){
		addMessage(LogLevel.ERROR, message, out, true);
	}
	
	public void error(String message, PrintStream out, Throwable ex){
		addMessage(LogLevel.ERROR, message, out, ex, true);
	}
	public void fatalError(String message, PrintStream out){
		addMessage(LogLevel.FATAL_ERROR, message, out, true);
	}
	
	public void fatalError(String message, PrintStream out, Throwable ex){
		addMessage(LogLevel.FATAL_ERROR, message, out, ex, true);
	}

	public void close() throws IOException {
		flush();
	}
}











